﻿using System;
using System.Collections.Generic;
using System.Text;
using limpalex.bbparser.Helpers;
using limpalex.bbparser.Tokens;
using limpalex.bbparser.Tree;

namespace limpalex.bbparser
{
    static class Tokenizer
    {
        private static readonly char[] AttributeValueDelimiters = new [] {' ', ']'};

        internal static IList<BaseToken> Tokenize(StringContainer original)
        {
            var tokens = new List<BaseToken>();

            while (original.NotEnded)
            {
                var index = original.FindIndex(Symbols.OpeningBrace);

                if (index == -1)
                {
                    BaseToken token = new TextToken(original.Substring());
                    tokens.Add(token);
                    break;
                }

                if (index != original.Position)
                {
                    BaseToken token = new TextToken(original.TakeUntil(index));
                    tokens.Add(token);
                }

                original.Next();

                ProcessTag(original, tokens);
            }

            return tokens;
        }

        private static void ProcessTag(StringContainer container, ICollection<BaseToken> tokens)
        {
            if ( container.Ended)
                return;
            var delimiter = container.AfterLettersOrDigits();
            if (container.Current == '/')
                delimiter = '/';
            var index = container.FindIndex(delimiter);
            var position = container.Position;
            TagToken token;

            if (index == -1)
            {
                container.SetPosition(position);
                tokens.Add(new TextToken(container.Substring()));
                container.ToEnd();
                return;
            }

            switch (delimiter)
            {
                case ']': //simple opening tag
                    token = new TagToken {Name = container.TakeUntil(index), IsClosing = false};
                    tokens.Add(token);
                    container.Next(); //skipping ']'
                    break;
                case '=': // tag with default attribute
                    token = new TagToken();
                    if ( index == container.Position )
                    {
                        container.SetPosition(position);
                        tokens.Add(new TextToken(container.TakeUntil(index + 1)));
                        return;
                        
                    }
                    token.Name = container.TakeUntil(index);
                    container.Next(); //skipping '='
                    if ( container.Ended)
                    {
                        container.SetPosition(position);
                        tokens.Add(new TextToken(container.Substring()));
                        container.ToEnd();
                        return;
                    }
                    var defValueDelimiter = ']';
                    if (container.Current == '\'')
                    {
                        defValueDelimiter = container.Current;
                        container.Next(); //skipping delimiter
                    }
                    if (container.Current == '\"')
                    {
                        defValueDelimiter = container.Current;
                        container.Next(); //skipping delimiter
                    }
                    index = container.FindIndex(defValueDelimiter, true);
                    if (index == -1) // malformed tag, rollback
                    {
                        TakeToEnd(container, tokens, position);
                        return;
                    }
                    if ( index == container.Position)
                    {
                        int tmpPos = index;
                        if (defValueDelimiter != ']')
                            tmpPos++;
                        tmpPos++;
                        container.SetPosition(position);
                        tokens.Add(new TextToken(container.TakeUntil(tmpPos)));
                        return;
                    }
                    token.DefaultValue = container.TakeUntil(index);
                    tokens.Add(token);
                    container.Next();
                    if ( defValueDelimiter == '\'' || defValueDelimiter == '\"')
                        container.Next(); // quote and brace are both delimiters
                    break;
                case ' ':
                    token = new TagToken {Name = container.TakeUntil(index)};
                    container.Next(); //skipping ' '
                    
                    while ( true )
                    {
                        index = container.FindIndex('=');
                        if ( index == -1)
                        {
                            container.SetPosition(position);
                            tokens.Add(new TextToken(container.Substring()));
                            container.ToEnd();
                            return;
                        }
                        var attribute = container.TakeUntil(index);
                        container.Next();
                        if (container.Current == '\'' || container.Current == '\"')
                        {
                            //delimiters = new char[] {container.Current};
                            container.Next(); //skipping delimiter
                            delimiter = container.Current;
                            index = container.FindIndex(delimiter, true);
                        }
                        else
                        {
                            delimiter = container.FindClosest(AttributeValueDelimiters);
                            index = container.FindIndex(delimiter);    
                        }
                        
                        if (index == -1)
                        {
                            container.SetPosition(position);
                            tokens.Add(new TextToken(container.Substring()));
                            container.ToEnd();
                            return;
                        }
                        var value = container.TakeUntil(index);
                        token.Attributes[attribute] = value;
                        if ( delimiter == '\'' || delimiter == '\"')
                            container.Next(); //delimiter
                        if ( delimiter == ']' )
                            break;
                    }
                    tokens.Add(token);

                    break;
                case '/':
                    container.Next(); //skipping '/'
                    index = container.FindIndex(']');
                    if ( index == -1) //malformed tag, skipping
                    {
                        TakeToEnd(container, tokens, position);
                        return;
                    }
                    token = new TagToken {Name = container.TakeUntil(index), IsClosing = true};
                    tokens.Add(token);
                    container.Next(); // skipping ']'
                    
                    break;
            }
        }

        internal static void TakeToEnd(StringContainer container, ICollection<BaseToken> tokens, int position)
        {
            container.SetPosition(position);
            tokens.Add(new TextToken(container.Substring()));
            container.ToEnd();
        }

        internal static bool HasAnyTextNodes(Branch current)
        {
            while ( true )
            {
                if ( current.NodesCount == 0 )
                    break;

                foreach (var n in current)
                {
                    if ( n.Type == NodeType.Leaf)
                    {
                        return true;
                    }

                    var branch = n as Branch;

                    if ( branch == null)
                        continue;
                    
                    if ( HasAnyTextNodes(branch))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}
